﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using SlamCms.Common;

namespace SlamCms.Data
{
    [Serializable]
	public class ContentTypeCollection : IEnumerable<Type>, ICloneable
	{
		private static readonly IDictionary<Type, string[]> contentTypeProperties = new Dictionary<Type, string[]>(); 
		private readonly IDictionary<string, Type> _contentTypes;

		public ContentTypeCollection()
		{
			_contentTypes = new Dictionary<string, Type>();
		}

        public object Clone()
        {
            return this.DeepClone();
        }

	    public IEnumerable<string> GetNames()
		{
			return _contentTypes.Keys;
		}

		public Type GetTypeByName(string name)
		{
			return _contentTypes[name];
		}

		public bool Contains(Type type)
		{
			return Contains(type.Name);
		}

		public bool Contains(string name)
		{
			return _contentTypes.ContainsKey(name);
		}

		public IList<Type> GetTypesByNames(string[] names)
		{
			return _contentTypes.Where(ct => ct.Key.In(names)).Select(ct => ct.Value).ToList();
		}

		public ContentTypeCollection Add<T>() where T : ContentItem, new()
		{
			return Add(typeof(T));
		}

		public ContentTypeCollection Add<T1, T2>()
			where T1 : ContentItem, new()
			where T2 : ContentItem, new()
		{
			return Add(typeof(T1), typeof(T2));
		}

		public ContentTypeCollection Add<T1, T2, T3>()
			where T1 : ContentItem, new()
			where T2 : ContentItem, new()
			where T3 : ContentItem, new()
		{
			return Add(typeof(T1), typeof(T2), typeof(T3));
		}

		public ContentTypeCollection Add<T1, T2, T3, T4>()
			where T1 : ContentItem, new()
			where T2 : ContentItem, new()
			where T3 : ContentItem, new()
			where T4 : ContentItem, new()
		{
			return Add(typeof(T1), typeof(T2), typeof(T3), typeof(T4));
		}

		public ContentTypeCollection Add<T1, T2, T3, T4, T5>()
			where T1 : ContentItem, new()
			where T2 : ContentItem, new()
			where T3 : ContentItem, new()
			where T4 : ContentItem, new()
			where T5 : ContentItem, new()
		{
			return Add(typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5));
		}

		public ContentTypeCollection Add<T1, T2, T3, T4, T5, T6>()
			where T1 : ContentItem, new()
			where T2 : ContentItem, new()
			where T3 : ContentItem, new()
			where T4 : ContentItem, new()
			where T5 : ContentItem, new()
			where T6 : ContentItem, new()
		{
			return Add(typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6));
		}

		public ContentTypeCollection Add<T1, T2, T3, T4, T5, T6, T7>()
			where T1 : ContentItem, new()
			where T2 : ContentItem, new()
			where T3 : ContentItem, new()
			where T4 : ContentItem, new()
			where T5 : ContentItem, new()
			where T6 : ContentItem, new()
			where T7 : ContentItem, new()
		{
			return Add(typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7));
		}

		public ContentTypeCollection Add<T1, T2, T3, T4, T5, T6, T7, T8>()
			where T1 : ContentItem, new()
			where T2 : ContentItem, new()
			where T3 : ContentItem, new()
			where T4 : ContentItem, new()
			where T5 : ContentItem, new()
			where T6 : ContentItem, new()
			where T7 : ContentItem, new()
			where T8 : ContentItem, new()
		{
			return Add(typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8));
		}

		public ContentTypeCollection Add(params Type[] types)
		{
			return Add(types.AsEnumerable());
		}

		public ContentTypeCollection Add(IEnumerable<Type> types)
		{
			if (types == null)
				throw new ArgumentNullException("types");

			foreach (var type in types)
			{
				if (!typeof(ContentItem).IsAssignableFrom(type))
					throw new Exception("The type {0} is not assignable from ContentItem.".F(type.Name));

				if (!_contentTypes.ContainsKey(type.Name))
					_contentTypes.Add(type.Name, type);
			}

			return this;
		}

		public ContentTypeCollection Clear()
		{
			_contentTypes.Clear();
			return this;
		}

		#region ContentType Properties

		public string[] GetProperties(string name, params string[] excludedProperties)
		{
			return GetProperties(name, excludedProperties.AsEnumerable());
		}

		public string[] GetProperties(string name, IEnumerable<string> excludedProperties)
		{
			return GetProperties(this._contentTypes[name], excludedProperties);
		}

		public static string[] GetProperties(Type type, params string[] excludedProperties)
		{
			return GetProperties(type, excludedProperties.AsEnumerable());	
		}

		public static string[] GetProperties(Type type, IEnumerable<string> excludedProperties)
		{
			if (type == null)
				throw new ArgumentNullException("type");
			if (excludedProperties == null)
				throw new ArgumentNullException("excludedProperties");
			
			string[] properties;
			if (!contentTypeProperties.TryGetValue(type, out properties))
			{
				var excludedPropertiesArray = excludedProperties.ToArray();
				properties = type.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance)
								 .Where(p => p.PropertyType.IsSystemType()
											 && !p.Name.In(excludedPropertiesArray))
								 .Select(p => p.Name)
								 .ToArray();
				contentTypeProperties[type] = properties;
			}

			return properties;
		}

		#endregion

		#region Enumerator

		public IEnumerator<Type> GetEnumerator()
		{
			return _contentTypes.Values.GetEnumerator();
		}

		IEnumerator IEnumerable.GetEnumerator()
		{
			return _contentTypes.Values.GetEnumerator();
		}

		#endregion
	}
}